package org.lc.cmd.impl;

import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.jline.console.CommandMethods;
import org.jline.reader.Completer;
import org.lc.cmd.CmdParam;
import org.lc.cmd.Command;
import org.lc.cmd.exception.CmdException;
import org.lc.cmd.exception.UnknownParamException;
import org.lc.cmd.impl.completer.SolonCommandCompleter;
import org.lc.cmd.util.ClassUtil;
import org.lc.cmd.util.CollectionUtil;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Supplier;
import java.util.stream.Collectors;

@Slf4j
public class SolonCommand extends Command {
    /**
     * 子命令map
     */
    @Getter
    protected final Map<String, SolonMethodCommand> subCmdMap;


    public SolonCommand(String name, Config config, Map<String, SolonMethodCommand> subCmdMap) {
        super(name, Collections.emptyList(), config);
        this.subCmdMap = subCmdMap;
    }

    /**
     * 执行命令
     *
     * @param args 参数
     * @return 参数
     */
    protected Object execute(String[] args) {
        if (args.length > 0) {
            SolonMethodCommand subCmd = subCmdMap.get(args[0]);
            if (subCmd != null) {
                return subCmd.execute(Arrays.copyOfRange(args, 1, args.length));
            }
        }
        if (subCmdMap.containsKey("")) {
            return subCmdMap.get("").execute(args);
        }
        if (args.length > 0) {
            throw new UnknownParamException(name, args[0]);
        }
        throw new CmdException("缺少子命令, 输入 \"help " + name + "\"来获取命令详细信息\n\t可选子命令：" + String.join(",",
                subCmdMap.keySet()));
    }

    protected List<Completer> complete() {
        return Collections.singletonList(new SolonCommandCompleter(this));
    }

    @Override
    public Object execute(Map<String, Object> args) {
        throw new UnsupportedOperationException();
    }

    @Override
    public CommandMethods toCommandMethods() {
        return new CommandMethods(input -> {
            return execute(input.args());
        }, name -> complete());
    }

    public static class SolonMethodCommand extends Command {
        private final Method method;

        private final ModelWarp[] models;

        /**
         * 获取对象
         */
        private final Supplier<Object> supplierObj;

        public SolonMethodCommand(String name, List<CmdParam> params, Config config, Method method,
                ModelWarp[] models, Supplier<Object> supplierObj) {
            super(name, params, config);
            this.method = method;
            this.models = models;
            this.supplierObj = supplierObj;
        }

        protected static Object caseCollection(Class<?> collectionType, List<Object> list) {
            if (collectionType.isInterface()) {
                if (Set.class.equals(collectionType)) {
                    return new HashSet<>(list);
                }
                return list;
            }
            try {
                Collection<Object> collection = ClassUtil.newInstance(collectionType);
                collection.addAll(list);
                return collection;
            } catch (Exception e) {
                throw new CmdException(e);
            }
        }

        /**
         * 执行命令
         *
         * @param args 参数
         * @return 参数
         */
        protected Object execute(String[] args) {
            return execute(config.getParser().parse(this, args));
        }

        @Override
        public Object execute(Map<String, Object> args) {
            Object[] params = parseParams(args);
            try {
                return method.invoke(supplierObj.get(), params);
            } catch (IllegalAccessException | InvocationTargetException e) {
                throw new IllegalStateException(e);
            }
        }

        protected Object[] parseParams(Map<String, Object> args) {
            Object[] realArgs = new Object[models.length];
            for (int i = 0; i < models.length; i++) {
                ModelWarp model = models[i];
                realArgs[i] = getModelValue(model, args);
            }
            return realArgs;
        }

        @SuppressWarnings("unchecked")
        protected Object getModelValue(ModelWarp model, Map<String, Object> args, int index) {
            if (model.isSingle()) {
                if (!args.containsKey(model.getName())) {
                    return null;
                }
                Object arg = args.get(model.getName());
                if (index != -1) {
                    arg = CollectionUtil.getOrNull((List<Object>) arg, index);
                }
                if (model.isCollection()) {
                    List<Object> list;
                    if (arg instanceof List) {
                        list = (List<Object>) arg;
                    } else {
                        list = Collections.singletonList(arg);
                    }
                    return caseCollection(model.getType(), list);
                }
                return arg;
            }
            List<ModelWarp> subModel =
                    model.getSubModel().stream().filter(m -> hasValue(m, args, index)).collect(Collectors.toList());
            if (CollectionUtil.isEmpty(subModel)) {
                return null;
            }
            if (model.isCollection() && index == -1) {
                List<Object> list = new ArrayList<>();
                for (int i = 0; ; i++) {
                    Object item = getModelValue(model, args, i);
                    if (item != null) {
                        list.add(item);
                    } else {
                        break;
                    }
                }
                return caseCollection(model.getType(), list);
            }
            try {
                Class<?> instanceType = model.isCollection() ? model.getItemType() : model.getType();
                Constructor<?> constructor = instanceType.getDeclaredConstructor();
                if (!constructor.isAccessible()) {
                    constructor.setAccessible(true);
                }
                Object obj = constructor.newInstance();
                for (ModelWarp warp : subModel) {
                    Object fieldVal = getModelValue(warp, args, index);
                    if (fieldVal != null) {
                        warp.getField().set(obj, fieldVal);
                    }
                }
                return obj;
            } catch (Exception e) {
                throw new CmdException(e);
            }
        }

        protected boolean hasValue(ModelWarp subModel, Map<String, Object> args, int index) {
            Object obj = args.get(subModel.getName());
            if (obj != null) {
                return index == -1 || (obj instanceof Collection && ((Collection<?>) obj).size() > index);
            } else {
                String prefix = subModel.getName() + "/";
                for (String key : args.keySet()) {
                    if (!key.startsWith(prefix)) {
                        continue;
                    }
                    if (index == -1) {
                        return true;
                    }
                    Object o = args.get(key);
                    if (o instanceof Collection) {
                        @SuppressWarnings("unchecked")
                        Collection<Object> list = (Collection<Object>) o;
                        if (list.size() > index) {
                            return true;
                        }
                    }
                }
            }
            return false;
        }

        protected Object getModelValue(ModelWarp model, Map<String, Object> args) {
            return getModelValue(model, args, -1);
        }
    }
}
